/*
 * TemplateFactory.c
 *
 *  Created on: 15.07.2011
 *      Author: sili
 */

#include "TemplateFactory.h"
#include "AALItem.h"

static log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("at.sili.aalsystem.factories.AudioFactory"));

static pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

TemplateFactory::TemplateFactory(Controller * _controller, const char * _configDir)
	: ActionListener("TemplateFactory"){

	AAL_DEBUG(logger,"Constructor");

	InitializeMagick(NULL);

	this->configDir.clear();
	this->configDir.append(_configDir);

	this->controller = _controller;
	this->controller->addListener(this);

	this->templateXmlFile.clear();
	this->templateXmlFile.append(_configDir);
	this->templateXmlFile.append("/templates.xml");

	// check if an templates.xml file exists
	char * targetName = canonicalize_file_name(templateXmlFile.c_str());
	if(!targetName){
		// file doesn't exists
		AAL_INFO(logger,"File: %s doesn't exists. Creating a new one.", templateXmlFile.c_str());
		serializeTemplatesFile();
	}
	delete(targetName);

	// check if template dir exists
	string templateDir(this->configDir);
	templateDir.append("/templates");
	targetName = canonicalize_file_name(templateDir.c_str());
	if(!targetName){
		// directory doesn't exists
		AAL_DEBUG(logger,"Creating directory %s.", templateDir.c_str());
		mkdir(templateDir.c_str(), 0777);
	}
	delete(targetName);

	// check if thumbnail dir exists
	string thumbnail(this->configDir);
	thumbnail.append("/");
	thumbnail.append(HTTP_DOCUMENT_ROOT);
	thumbnail.append("/images/thumbnails/");
	targetName = canonicalize_file_name(thumbnail.c_str());
	if(!targetName){
		// directory doesn't exists
		AAL_DEBUG(logger,"Creating directory %s.", thumbnail.c_str());
		mkdir(thumbnail.c_str(), 0777);
	}
	delete(targetName);

	deSerializeTemplatesFile();
}

TemplateFactory::~TemplateFactory(){
	AAL_DEBUG(logger,"Destructor");
}

void TemplateFactory::serializeTemplatesFile(){

	pthread_rwlock_rdlock(&rwlock);

	AAL_DEBUG(logger,"SerializeTemplateFile");

	char integerBuffer[100];
	XMLCh tempStr0[100];
	XMLCh tempStr1[100];

	XMLString::transcode("LS", tempStr0, 99);
	DOMImplementation * impl = DOMImplementationRegistry::getDOMImplementation(tempStr0);
	XMLString::transcode("Templates", tempStr0, 99);
	DOMDocument * doc = impl->createDocument(0, tempStr0, 0);

	map<string, Template *>::iterator itemValuePropIt;
	for (itemValuePropIt=this->templates.begin(); itemValuePropIt!=this->templates.end(); itemValuePropIt++ ){
		// string catStr = itemValuePropIt->first;
		Template * prop = itemValuePropIt->second;
		AAL_DEBUG(logger,"Creating entry:%s", prop->name.c_str());

		XMLString::transcode("Template", tempStr0, 99);
		DOMElement * item = doc->createElement(tempStr0);

		XMLString::transcode("name", tempStr0, 99);
		XMLString::transcode(prop->name.c_str(), tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		snprintf(integerBuffer, (size_t)99, "%ld", prop->templateCreation);
		XMLString::transcode("templateCreation", tempStr0, 99);
		XMLString::transcode(integerBuffer, tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		XMLString::transcode("unit", tempStr0, 99);
		XMLString::transcode(prop->unit.c_str(), tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		snprintf(integerBuffer, (size_t)99, "%d", prop->minValue);
		XMLString::transcode("minValue", tempStr0, 99);
		XMLString::transcode(integerBuffer, tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		snprintf(integerBuffer, (size_t)99, "%d", prop->maxValue);
		XMLString::transcode("maxValue", tempStr0, 99);
		XMLString::transcode(integerBuffer, tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		snprintf(integerBuffer, (size_t)99, "%d", prop->step);
		XMLString::transcode("step", tempStr0, 99);
		XMLString::transcode(integerBuffer, tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		XMLString::transcode("minName", tempStr0, 99);
		XMLString::transcode(prop->minName.c_str(), tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		XMLString::transcode("maxName", tempStr0, 99);
		XMLString::transcode(prop->maxName.c_str(), tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		XMLString::transcode("switchOnName", tempStr0, 99);
		XMLString::transcode(prop->switchOnName.c_str(), tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		XMLString::transcode("switchOffName", tempStr0, 99);
		XMLString::transcode(prop->switchOffName.c_str(), tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		XMLString::transcode("increaseName", tempStr0, 99);
		XMLString::transcode(prop->increaseName.c_str(), tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		XMLString::transcode("decreaseName", tempStr0, 99);
		XMLString::transcode(prop->decreaseName.c_str(), tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		doc->getDocumentElement()->appendChild(item);
	}

	Tools::createXmlFileFromDOMDocument(this->templateXmlFile.c_str(),doc);
	doc->release();
	AAL_INFO(logger,"File: %s created.", this->templateXmlFile.c_str());

	pthread_rwlock_unlock(&rwlock);

}

void TemplateFactory::deSerializeTemplatesFile(){
	pthread_rwlock_rdlock(&rwlock);
	AAL_DEBUG(logger,"File: %s", this->templateXmlFile.c_str());
	XercesDOMParser parser;
	DOMDocument * doc = Tools::createDOMFromXmlFile(this->templateXmlFile.c_str(), &parser);
	if(doc != NULL){
		DOMNodeList * childNodes = doc->getDocumentElement()->getChildNodes();
		for(XMLSize_t childIndex = 0; childIndex < childNodes->getLength(); ++childIndex ){
			DOMNode * childNode = childNodes->item(childIndex);

			char * value = XMLString::transcode(childNode->getNodeName());
			if(strcmp(value,"Template") != 0){
				continue;
			}
			XMLString::release(&value);

			DOMElement * child = (DOMElement*)childNodes->item(childIndex);

			string name = "";
			Tools::getStringAttributeValue(child, "name", &name);

			Template * prop = this->createOrGetExistingTemplate(name.c_str());

			prop->templateCreation = Tools::getIntAttributeValue(child, "templateCreation");;

			string unit = "";
			Tools::getStringAttributeValue(child, "unit", &unit);
			prop->unit = unit;

			prop->minValue = Tools::getIntAttributeValue(child, "minValue");
			prop->maxValue = Tools::getIntAttributeValue(child, "maxValue");
			prop->step = Tools::getIntAttributeValue(child, "step");

			string minName = "";
			Tools::getStringAttributeValue(child, "minName", &minName);
			prop->minName = minName;

			string maxName = "";
			Tools::getStringAttributeValue(child, "maxName", &maxName);
			prop->maxName = maxName;

			string switchOnName = "";
			Tools::getStringAttributeValue(child, "switchOnName", &switchOnName);
			prop->switchOnName = switchOnName;

			string switchOffName = "";
			Tools::getStringAttributeValue(child, "switchOffName", &switchOffName);
			prop->switchOffName = switchOffName;

			string increaseName = "";
			Tools::getStringAttributeValue(child, "increaseName", &increaseName);
			prop->increaseName = increaseName;

			string decreaseName = "";
			Tools::getStringAttributeValue(child, "decreaseName", &decreaseName);
			prop->decreaseName = decreaseName;
		}
	} else {
		AAL_WARN(logger,"Can't deserialize file: %s", this->templateXmlFile.c_str());
	}
	pthread_rwlock_unlock(&rwlock);
}

Template * TemplateFactory::createOrGetExistingTemplate(const char * _catName){
	if(_catName == NULL){
		AAL_WARN(logger,"Cant't return template for an empty _templateName");
		return NULL;
	}

	string catName(_catName);

	Template * existingTemplate = getTemplate(_catName);
	if(!existingTemplate){
		// create new
		AAL_DEBUG(logger,"Creating new template %s", _catName);
		Template * prop = new Template();
		prop->name = _catName;
		this->templates.insert(pair<string,Template *>(catName, prop));
	}

	map<string,Template *>::iterator it = this->templates.find(catName);
	return (it->second);
}


Template * TemplateFactory::getTemplate(const char * _catName){
	if(_catName == NULL){
		AAL_WARN(logger,"Cant't return template for an empty _templateName");
		return NULL;
	}

	string catName(_catName);
	map<string,Template *>::iterator it = this->templates.find(catName);
	if(it != this->templates.end()){
		// use existing one
		return (it->second);
	}
	return NULL;
}

bool TemplateFactory::deleteTemplate(const char * _templateName, string * _response){
	char resBuff[500];
	string templateName(_templateName);
	if(this->templates.erase(templateName) == 1){
		serializeTemplatesFile();
		sprintf(resBuff,"Template %s deleted", _templateName);
		AAL_INFO(logger, resBuff);
		_response->append(resBuff);
		return true;
	} else {
		sprintf(resBuff,"Can't delete template %s", _templateName);
		AAL_ERROR(logger, resBuff);
		_response->clear();
		_response->append(resBuff);
		return false;
	}
}

bool TemplateFactory::adoptTemplate(Template * _template, const char * _oldName, string * _response){
	string response;
	char resBuff[500];

	AAL_DEBUG(logger,"Adopt template (old)name:%s (new)name:%s", _oldName, _template->name.c_str());

	// Template name changed !!
	if(strcmp(_oldName,_template->name.c_str()) != 0){

		// make sure that the new name doesn't exists
		Template * temp = getTemplate(_template->name.c_str());
		if(temp != NULL){
			sprintf(resBuff,"Can't adopt Template %s cause new name %s already exists. Please rename or delete %s.", _oldName, temp->name.c_str(), temp->name.c_str());
			AAL_WARN(logger, resBuff);
			_response->append(resBuff);
			return false;
		}

		string oldImg(this->configDir);
		oldImg.append("/templates/");
		oldImg.append(_oldName);
		oldImg.append(".png");

		string newImg(this->configDir);
		newImg.append("/templates/");
		newImg.append(_template->name.c_str());
		newImg.append(".png");

		bool renameResult = Tools::renameFile(oldImg.c_str(), newImg.c_str(), &response);
		if(!renameResult){
			_response->append(response.c_str());
			AAL_WARN(logger, response.c_str());
			return false;
		}

		string oldThumbnail(this->configDir);
		oldThumbnail.append("/");
		oldThumbnail.append(HTTP_DOCUMENT_ROOT);
		oldThumbnail.append("/images/thumbnails/");
		oldThumbnail.append(_oldName);
		oldThumbnail.append("_thumbnail.png");

		string newThumbnail(this->configDir);
		newThumbnail.append("/");
		newThumbnail.append(HTTP_DOCUMENT_ROOT);
		newThumbnail.append("/images/thumbnails/");
		newThumbnail.append(_template->name.c_str());
		newThumbnail.append("_thumbnail.png");

		renameResult = Tools::renameFile(oldThumbnail.c_str(), newThumbnail.c_str(), &response);
		if(!renameResult){
			_response->append(response.c_str());
			AAL_WARN(logger, response.c_str());
			return false;
		}
	}

	Template * temp = getTemplate(_oldName);
	if(temp){
		temp->name = _template->name;
		temp->templateCreation = _template->templateCreation;
		temp->unit = _template->unit;
		temp->minValue = _template->minValue;
		temp->maxValue = _template->maxValue;
		temp->step = _template->step;
		temp->minName = _template->minName;
		temp->maxName = _template->maxName;
		temp->switchOnName = _template->switchOnName;
		temp->switchOffName = _template->switchOffName;
		temp->increaseName = _template->increaseName;
		temp->decreaseName = _template->decreaseName;

		this->serializeTemplatesFile();

		sprintf(resBuff,"Template %s successfully updated.", temp->name.c_str());
		AAL_INFO(logger, resBuff);
		_response->append(resBuff);
		return true;
	} else {
		sprintf(resBuff,"Can't find old template %s. Can't update.", _oldName);
		AAL_WARN(logger, resBuff);
		_response->append(resBuff);
		return false;
	}
}

void TemplateFactory::getTemplate(const char * _templateName, Image & _copyInto){

	Template * temp = getTemplate(_templateName);
	if(temp != NULL){

		// check if the template is already loaded
		if(!temp->templateImage.isValid()){
			AAL_DEBUG(logger,"Loading template image for template:%s", _templateName);

			string path(this->configDir);
			path.append("/templates/");
			path.append(_templateName);
			path.append(".png");

			// check if the template file exists
			char * targetName = canonicalize_file_name(path.c_str());
			if(!targetName){
				AAL_WARN(logger,"Template file %s doesn't exists", path.c_str());
				delete(targetName);
				return;
			}
			temp->templateImage = Image(targetName);
			delete(targetName);
		}

		if(temp->templateImage.isValid()){
			_copyInto = temp->templateImage;
		} else {
			AAL_WARN(logger,"Can't get a valid templateImage for template:%s", _templateName);
			return;
		}
	}
}

void TemplateFactory::createTemplate(DOMDocument * _doc, DOMNode * _selfSvgNode, const char * _svgFile, const map<string, AALArea> & _svgAreas, time_t _templateCreation){

	DOMNodeList * childNodes = _selfSvgNode->getChildNodes();
	for(XMLSize_t childIndex = 0; childIndex < childNodes->getLength(); ++childIndex ){
		DOMNode * childNode = childNodes->item(childIndex);
		createTemplate(_doc, childNode,_svgFile, _svgAreas,_templateCreation);
	}

	// create template for self
	string templateName = "";
	string svgElementId = "";
	Tools::getStringAttributeValue(_selfSvgNode, "aal:templateName", &templateName);
	Tools::getStringAttributeValue(_selfSvgNode, "id", &svgElementId);
	if(templateName.size() > 0 && svgElementId.size() > 0){
		Template * temp = createOrGetExistingTemplate(templateName.c_str());
		if(temp != NULL){
			// CREATE TEMPLATE ONLY IF IT WAS NOT CREATED IN THIS SESSION
			if(_templateCreation > temp->templateCreation){
				// CREATE NEW ONE
				AAL_INFO(logger,"Creating/updating template %s", templateName.c_str());

				map<string, AALArea>::const_iterator finder = _svgAreas.find(svgElementId);
				if(finder != _svgAreas.end()){
					AALArea matchArea = finder->second;

					string path(this->configDir);
					path.append("/templates/");
					path.append(templateName.c_str());
					path.append(".png");

					double width = matchArea.width;
					double height = matchArea.height;

					AAL_INFO(logger,"Template width:%f height:%f",width ,height);

					cOsdProvider::UpdateOsdSize();
					double ratio = 1.0;

					/*
					// TEMPLATE FILE DOESN'T NEED TO BE BIGGER AS THE AVAIABLE OSD.
					// INFO: here we get always nothing bigger as 648 width.
					//       so, ignore this and do an HDTV resuolution hardcoded 1920x1080
					if(width > height){
						ratio = cOsd::OsdWidth() / width;
					} else {
						ratio = cOsd::OsdHeight() / height;
					}
					*/

					if(width > height){
						ratio = 1920.0 / width;
					} else {
						ratio = 1080.0 / height;
					}

					int w = int(floor(width * ratio));
					int h = int(floor(height * ratio));

					char cmd[500];
					snprintf(cmd, (size_t)499, "inkscape -z --export-id=%s --export-id-only -w %d -h %d -e %s %s",
							svgElementId.c_str(), w, h, path.c_str(), _svgFile);
					AAL_DEBUG(logger,"Executing cmd: %s", cmd);

					const char * msg = "Creating template file: %s";
					char logBuffer [sizeof(msg) + 200];
					sprintf(logBuffer,msg,path.c_str());

					this->controller->publish(doLog,string(logBuffer).c_str());
					string cmdResult = "";
					Tools::execCmd(cmd, &cmdResult);

					// CREATE THUMBNAIL FOR THE WEB FRONTEND
					AAL_DEBUG(logger,"Creating Thumbnail for the webfrontend");
					string thumbnail(this->configDir);
					thumbnail.append("/");
					thumbnail.append(HTTP_DOCUMENT_ROOT);
					thumbnail.append("/images/thumbnails/");
					thumbnail.append(templateName.c_str());
					thumbnail.append("_thumbnail.png");

					Image thumbnailImage;
					getTemplate(templateName.c_str(), thumbnailImage);
					thumbnailImage.scale(Magick::Geometry(100, 100,0,0,false,false));
					thumbnailImage.write(thumbnail.c_str());


					temp->templateCreation = _templateCreation;
				} else {
					AAL_WARN(logger,"Can't get area for svgElementId:%s", svgElementId.c_str());
				}
			} else {
				AAL_INFO(logger,"Don't need to create/update template %s cause this session did it before.", templateName.c_str());
			}
		} else {
			AAL_WARN(logger,"Can't get template from TemplateFactory for templateName: %s", templateName.c_str());
		}
		setVisibility((DOMElement *)_selfSvgNode, false);
		// save changed visibility template
		Tools::createXmlFileFromDOMDocument(_svgFile,_doc);
	}
}

void TemplateFactory::setVisibility(DOMElement * _selfSvgNode, const bool _visible){
	string newStyle;
	if(_visible){
		newStyle.append("display:inline;");
	} else {
		newStyle.append("display:none;");
	}

	// get the style attribute. e.g. style=display:inline;enable-background:new
	string buffer = "";
	Tools::getStringAttributeValue(_selfSvgNode, "style", &buffer);


	vector<string> rows;
	Tools::split((char*)buffer.c_str(), ";", &rows);
	for (unsigned int rowIdx=0; rowIdx < rows.size(); rowIdx++){
		char * row = (char*)rows[rowIdx].c_str();
		if(strstr(row,"display:") == NULL){
			newStyle.append(row);
		}
	}
	AAL_DEBUG(logger,"SetVisibility setting style to %s",newStyle.c_str());
	XMLCh name[10];
	XMLCh value[500];
	XMLString::transcode("style", name, 9);
	XMLString::transcode(newStyle.c_str(), value, 499);
	_selfSvgNode->setAttribute(name,value);
}

void TemplateFactory::Action(void){
	if(this->templateActions.size() > 0){
		AALAction * action = this->templateActions.back();
		if(action->type == doTemplate){
			const char * msg = "Creating template images from svg file:%s task identifier:%s";
			char logBuffer [sizeof(msg) + 200];
			sprintf(logBuffer,msg,action->message.c_str(), action->data.c_str());
			this->controller->publish(doLog,string(logBuffer).c_str());

			string buffer = "";
			XercesDOMParser parser;
			DOMDocument * doc = Tools::createDOMFromXmlFile(action->message.c_str(), &parser);
			if(doc == NULL){
				AAL_ERROR(logger,"Exception while reading SVG file:%s ", action->message.c_str());
				this->templateActions.pop_back();

				// ERROR WHILE READING SVG FILE
				char message [200];
				sprintf(message,"Task %s failed",action->data.c_str());
				this->controller->publish(doLog,message);

				delete(action);
				return;
			}
			DOMElement * root = doc->getDocumentElement();

			char cmd[500];
			snprintf(cmd, (size_t)499, "inkscape --query-all %s", action->message.c_str());
			AAL_DEBUG(logger,"Executing cmd %s.", cmd);
			string cmdResult = "";
			Tools::execCmd(cmd, &cmdResult);

			// get areas (x,y,width, height) for every element in this svg
			map<string, AALArea> areas;

			vector<string> rows;
			Tools::split((char*)cmdResult.c_str(), "\n", &rows);
			for (unsigned int rowIdx=0; rowIdx < rows.size(); rowIdx++){
				char * row = (char*)rows[rowIdx].c_str();
				vector<string> cols;
				Tools::split(row, ",", &cols);
				AALArea area;
				area.left =Tools::getDoubleFromString(cols[1].c_str());
				area.top =Tools::getDoubleFromString(cols[2].c_str());
				area.width =Tools::getDoubleFromString(cols[3].c_str());
				area.height =Tools::getDoubleFromString(cols[4].c_str());
				areas.insert(pair<string,AALArea>(cols[0],area));
			}

			createTemplate(doc, root, action->message.c_str(), areas, time(NULL));
			serializeTemplatesFile();

			const char * msg1 = "Template images for all possible elements of the given file %s created.";
			char logBuffer1 [sizeof(msg1) + 200];
			sprintf(logBuffer1,msg1,action->message.c_str());
			this->controller->publish(doLog,string(logBuffer1).c_str());

			// ERROR WHILE IMPORTING SVG FILE
			char message [200];
			sprintf(message,"Task %s done",action->data.c_str());
			this->controller->publish(doLog,message);

		}
		this->templateActions.pop_back();
		delete(action);
	}
}

void TemplateFactory::getFlatList(list<Template *> * _list, int _currentLevel){
	map<string, Template *>::iterator templateIterator;
	for (templateIterator=this->templates.begin(); templateIterator!=this->templates.end(); templateIterator++ ){
		Template * temp = templateIterator->second;
		_list->push_back(temp);
	}
}

static int imageFilter(const struct dirent * _d){
	string fileName(_d->d_name);
	if(fileName.find(".png") != string::npos)
		return 1;
	return 0;
}


void TemplateFactory::getTemplateImagesAsJSON(string * _fillInto){

	string templateDir(this->configDir);
	templateDir.append("/templates");

	_fillInto->clear();
	_fillInto->append("[");

	struct dirent ** eps;
	int n;
	n = scandir (templateDir.c_str(), &eps, imageFilter, alphasort);
	if (n >= 0){
		int cnt;
		for (cnt = 0; cnt < n; ++cnt){
			struct stat attrib;								// create a file attribute structure
			string absolutePath(templateDir.c_str());
			absolutePath.append("/");
			absolutePath.append(eps[cnt]->d_name);
			stat(absolutePath.c_str(), &attrib);			// get the attributes of afile.txt

			char buffer[300];
			string st = (string) "" + ctime(&attrib.st_ctime);
			// remove linefeed from ctime() output
			unsigned int p;
			if((p = st.find('\n')) != string::npos) {
				st = st.substr(0,p);
			}
			sprintf(buffer, "{\"filename\": \"%s\",\"date\": \"%s\"}", eps[cnt]->d_name, st.c_str());
			_fillInto->append(buffer);
			if(cnt < n - 1){
				_fillInto->append(",");
			}
		}
	}
	_fillInto->append("]\n");
}




void TemplateFactory::doAction(AALAction * _action){
	if(_action->type == doTemplate){
		AAL_INFO(logger, "%s doAction id:%ld type: %s message:\"%s\" aalItemId:%d", ((ActionListener*)this)->getName(),_action->id, _action->typeToString(), _action->message.c_str(), _action->aalItem != NULL ? _action->aalItem->getId():-1);
		pthread_rwlock_rdlock(&rwlock);
		AALAction * newAction = new AALAction(_action->type, _action->aalItem, _action->message.c_str(), 0, this->controller);
		newAction->data = _action->data;
		this->templateActions.push_back(newAction);
		pthread_rwlock_unlock(&rwlock);
		Start();
	}
}

void TemplateFactory::doMainSavedAction(){

}
